package com.retrozelda.nyanimals;

import java.util.Date;
import java.util.Hashtable;
import java.util.Random;

import com.retrozelda.nyanimals.Animations.CAnimation;

import android.graphics.RectF;
import android.util.Log;

public class CNyanimal extends CRectangle
{
	private static final String TAG = CNyanimal.class.getSimpleName();
	

	// ////////// Utilities  /////////////////////
	// ////////////////////////////////////////////////////////	
	int screenWidth;
	int screenHeight;
	
	void UpdateScreenData(int width, int height)
	{
		screenWidth = width;
		screenHeight = height;
	}
	
	// ////////////////////////////////////////////////////////
	// ////////////////////////////////////////////////////////

	// ////////// Nyanimal Actions/Status /////////////////////
	// ////////////////////////////////////////////////////////
	enum NyanimalStates { IDLE, EATING, SLEEPING, WALKING, TOTAL };
	enum NyanimalStatus { HAPPY, HUNGRY, SLEEPY, BORED, SICK, DEAD }; // determines different "idle" animations
	enum NyanimalStages 
	{ 
		EGG, BABY, CHILD, TEEN, ADULT, SENIOR; 
		
		// gets the age that the particular stage starts
		public int GetStartAge()
		{
			switch(this)
			{
			case EGG:
				return -1;
			case BABY:
				return 0;
			case CHILD:
				return 5;
			case TEEN:
				return 13;
			case ADULT:
				return 25;
			case SENIOR:
				return 65;
			}		
			return -2;
		}		
	};

	// NOTE: if you add here, you need to add in clone()
	NyanimalStates	m_curState;
	NyanimalStatus	m_curStatus;
	NyanimalStages	m_curStage;
	Date			m_birthDay;
	char			m_curAge;		// in days
	
	// NOTE: when anything is < 0 the happy meter goes down, and everything > 0 makes the meter go up
	// happy meter goes down faster with multiple problems, and goes up at a constant rate
	float			m_happyMeter;	// 100 is happy,	 0 is unhappy,		// -100 mean dead
	float			m_healthMeter;	// 100 is healthy,	 0 is unhealthy,	// medicine brings to 100
	float			m_hungerMeter;	// 100 is full,		 0 is hungry,		// goes down slower while sleeping
	float			m_tiredMeter;	// 100 is sleepy, 	 0 is wide awake,	// sleep brings to 100
	float			m_boredMeter;	// 100 is bored,	 0 is content,		
	
	float GetmeterMin() { return  0.0f;}
	float GetMeterMax() { return  100.0f;}
	float GetHappyMeter() { return m_happyMeter;}
	float GetHealthMeter() { return m_healthMeter;}
	float GetHungerMeter() { return m_hungerMeter;}
	float GetTiredMeter() { return m_tiredMeter;}
	float GetBoredMeter() { return m_boredMeter;}

	// ////////////////////////////////////////////////////////
	// ////////////////////////////////////////////////////////

	// ///////////////// animations/movement ///////////////////////////
	// ////////////////////////////////////////////////////////
	enum NyanimalAnimations { IDLE, EATING, SLEEPING, WALKING_UP, WALKING_DOWN, WALKING_LEFT, WALKING_RIGHT, HUNGRY, SLEEPY, BORED, SICK, DEAD};
	enum Direction { 
		UP, DOWN, LEFT, RIGHT;
		static Direction GetDirectionByValue(int val)
		{
			int dir = val % 4; // safety filter
			switch(dir)
			{
			case 0:
				return UP;
			case 1:
				return DOWN;
			case 2:
				return LEFT;
			case 3:
				return RIGHT;
			}
			return DOWN;
		}
	};
	Random m_random;
	Hashtable<NyanimalAnimations, CAnimation> m_Animations;
	NyanimalAnimations m_curAnimation;
	Direction m_curDirection;
	float m_walkSpeed;
	float m_timeInState;
	int m_destinationPoint; // based of direction
	
	
	private void DeterminNextAction()
	{
		// determin the new thing to do
		int decision = m_random.nextInt() & 0x1; // walk, idle
		NyanimalStates newState = (decision == 0?NyanimalStates.WALKING:NyanimalStates.IDLE);
		Direction newDirection = Direction.GetDirectionByValue(m_random.nextInt());
		
		// first time is always explicit
		if(m_curState == NyanimalStates.TOTAL)
		{
			newState = NyanimalStates.IDLE;
		}
		
		if(newState == NyanimalStates.IDLE)
		{
			m_timeInState = m_random.nextFloat() * 5.0f + 5.0f;
		}
		if(newState == NyanimalStates.WALKING)
		{
			// keep object creating outside of loops			
			int minX = (int)m_scale[0];
			int maxX = screenWidth - (int)m_scale[0];
			int minY = (int)m_scale[1];
			int maxY = screenHeight - (int)m_scale[1];
			int[] projPos = getPosition();
			int newPositionOffset = 0;
			
			// keep going until we get a good position
			boolean bAction = false;
			do
			{
				// determine the new position
				projPos = getPosition();
				newPositionOffset = m_random.nextInt(130) + 20;
				
				switch(newDirection)
				{
				case UP:
					projPos[1] -= newPositionOffset;
					m_destinationPoint = projPos[1];
					break; 
				case DOWN:
					projPos[1] += newPositionOffset;
					m_destinationPoint = projPos[1];
					break;
				case LEFT:
					projPos[0] -= newPositionOffset;
					m_destinationPoint = projPos[0];
					break; 
				case RIGHT:
					projPos[0] += newPositionOffset;
					m_destinationPoint = projPos[0];
					break;
				}
				
				// check if the new position is valid					
				if((projPos[0] >= minX) && (projPos[1] >= minY) && (projPos[0] <= maxX) && (projPos[1] <= maxY))
				{
					bAction = true;
				}
				else
				{
					newDirection = Direction.GetDirectionByValue(newDirection.ordinal() + 1);
				}
				
			} while(bAction == false);
		}
		
		ChangeState(newState, newDirection);
	}
	
	// ////////////////////////////////////////////////////////
	// ////////////////////////////////////////////////////////

	public CNyanimal()
	{
		super();
		m_Animations = new Hashtable<NyanimalAnimations, CAnimation>();
		m_random = new Random();
	}

	@Override
	public Object clone()
	{
		CNyanimal ret = null;
		ret = (CNyanimal)super.clone();
		
		// state-related
		ret.m_curState = m_curState;
		ret.m_curStatus = m_curStatus;
		ret.m_curStage = m_curStage;
		ret.m_birthDay = (Date)m_birthDay.clone();
		ret.m_curAge = m_curAge;		// in days
		ret.m_happyMeter = m_happyMeter;	// 100 is happy,	 0 is unhappy,		// -100 mean dead
		ret.m_healthMeter = m_healthMeter;	// 100 is healthy,	 0 is unhealthy,	// medicine brings to 100
		ret.m_hungerMeter = m_hungerMeter;	// 100 is full,		 0 is hungry,		// goes down slower while sleeping
		ret.m_tiredMeter = m_tiredMeter;	// 100 is sleepy, 	 0 is wide awake,	// sleep brings to 100
		ret.m_boredMeter = m_boredMeter;	// 100 is bored,	 0 is content,		

		// game-related
		ret.m_random = new Random();
		ret.m_Animations = new Hashtable<NyanimalAnimations, CAnimation>(m_Animations);
		ret.m_curAnimation = m_curAnimation;
		ret.m_curDirection = m_curDirection;
		ret.m_walkSpeed = m_walkSpeed;
		ret.m_timeInState = m_timeInState;
		ret.m_destinationPoint = m_destinationPoint; // based of direction
		
		
		return ret;
	}
	
	public void Init(NyanimalGLSurfaceView game)
	{
		Log.d(TAG, "Nyanimal Init Start");
		super.Init(game);
		
		// init the utilities
		int frameCount = 0;
		UpdateScreenData(game.getWidth(), game.getHeight());
		
		// idle
		CAnimation AnimIdle = new CAnimation("Idle", true);
		for(frameCount = 0; frameCount < 8; ++frameCount)
		{
			AnimIdle.AddFrame(frameCount * 64, 0, 64, 64, 0.5f);
		}
		m_Animations.put(NyanimalAnimations.IDLE, AnimIdle);
		
		// sleeping
		CAnimation AnimSleep = new CAnimation("Sleep", true);
		for(frameCount = 0; frameCount < 8; ++frameCount)
		{
			AnimSleep.AddFrame(frameCount * 64, 64, 64, 64, 0.5f);
		}
		m_Animations.put(NyanimalAnimations.SLEEPING, AnimSleep);

		// eating
		CAnimation AnimEat = new CAnimation("Eat", true);
		for(frameCount = 0; frameCount < 3; ++frameCount)
		{
			AnimEat.AddFrame(frameCount * 64, 128, 64, 64, 0.5f);
		}
		m_Animations.put(NyanimalAnimations.EATING, AnimEat);
		
		// hungry
		CAnimation AnimHungry = new CAnimation("Hungry", true);
		for(frameCount = 3; frameCount < 6; ++frameCount)
		{
			AnimHungry.AddFrame(frameCount * 64, 128, 64, 64, 0.5f);
		}
		m_Animations.put(NyanimalAnimations.HUNGRY, AnimHungry);
		// move right
		CAnimation animRight = new CAnimation("Walk_Right", true);
		for(frameCount = 0; frameCount < 2; ++frameCount)
		{
			animRight.AddFrame(frameCount * 64, 192, 64, 64, 0.25f);
		}
		m_Animations.put(NyanimalAnimations.WALKING_RIGHT, animRight);
		// move left
		CAnimation animLeft = new CAnimation("Walk_Left", true);
		for(frameCount = 2; frameCount < 4; ++frameCount)
		{
			animLeft.AddFrame(frameCount * 64, 192, 64, 64, 0.25f);
		}
		m_Animations.put(NyanimalAnimations.WALKING_LEFT, animLeft);
		// move down
		CAnimation animDown = new CAnimation("Walk_Down", true);
		for(frameCount = 4; frameCount < 6; ++frameCount)
		{
			animDown.AddFrame(frameCount * 64, 192, 64, 64, 0.25f);
		}
		m_Animations.put(NyanimalAnimations.WALKING_DOWN, animDown);
		// move up
		CAnimation animUp = new CAnimation("Walk_Up", true);
		for(frameCount = 6; frameCount < 8; ++frameCount)
		{
			animUp.AddFrame(frameCount * 64, 192, 64, 64, 0.25f);
		}
		m_Animations.put(NyanimalAnimations.WALKING_UP, animUp);
		
		// default speed
		m_walkSpeed = 100.0f;
		
		// default position 
		SetPosition((int)(0.5f * game.getWidth()), (int)(0.75f * game.getHeight()));
		
		// load in our texture
		LoadTexture(R.drawable.nyanimal_sheet, game);
		
		// load in the Nyanimal
		LoadNyanimal();
		
		DeterminNextAction();
		Log.d(TAG, "Nyanimal Init End");
	}
	
	private void LoadNyanimal()
	{
		Log.d(TAG, "Nyanimal Load Start");
		m_curState = NyanimalStates.TOTAL; // is total now so first DeterminNextAction() call doesnt end in infinite loop if it doesnt land on idle
		m_curStatus = NyanimalStatus.HAPPY;
		m_curStage = NyanimalStages.EGG;
		m_curAge = 0;					
		m_happyMeter = 100;	
		m_healthMeter = 100;	
		m_hungerMeter = 100;	
		m_tiredMeter = 100;	
		m_boredMeter = 100;	
		m_birthDay = new Date(1992, 03, 16);
		Log.d(TAG, "Nyanimal Load End");
	}
	
	/*
	 * 
	enum NyanimalStates { IDLE, EATING, SLEEPING, WALKING, TOTAL };
	enum NyanimalStatus { HAPPY, HUNGRY, SLEEPY, BORED, SICK, DEAD }; // determines different "idle" animations
	 * 
	 * */
	public void ChangeState(NyanimalStates state)
	{
		ChangeState(state, null);
	}
	public void ChangeState(NyanimalStates state, Direction dir)
	{
		Log.d(TAG, "Changing to state: " + state + " facing: " + dir);
		m_curState = state;
		
		if(dir == null)
		{
			dir = m_curDirection;
		}
		m_curDirection = dir;
		
		// determine our animation
		NyanimalAnimations newAnim = null;
		switch(m_curState)
		{
		case IDLE:
			switch(m_curStatus)
			{
				case HAPPY:				
					newAnim = NyanimalAnimations.IDLE;	
				break;
				case HUNGRY:
					newAnim = NyanimalAnimations.HUNGRY;
				break;
				case SLEEPY:
					newAnim = NyanimalAnimations.SLEEPY;
				break;
				case BORED:
					newAnim = NyanimalAnimations.BORED;
				break;
				case SICK:
					newAnim = NyanimalAnimations.SICK;
				break;
				case DEAD:
					newAnim = NyanimalAnimations.DEAD;
				break;
			}
			break;
		case EATING:
			newAnim = NyanimalAnimations.EATING;
			break;
		case SLEEPING:
			newAnim = NyanimalAnimations.SLEEPING;
			break;
		case WALKING:
			switch(m_curDirection)
			{
			case UP:
				newAnim = NyanimalAnimations.WALKING_UP;
				break;
			case DOWN:
				newAnim = NyanimalAnimations.WALKING_DOWN;
				break;
			case LEFT:
				newAnim = NyanimalAnimations.WALKING_LEFT;
				break;
			case RIGHT:
				newAnim = NyanimalAnimations.WALKING_RIGHT;
				break;
			};
			break;
		};
		
		// ensure the animation doesnt start over
		if(newAnim != m_curAnimation)
		{
			m_Animations.get(newAnim).Reset();
		}
		m_curAnimation = newAnim;
		UpdateUV();
	}

	@Override
	public void Update(float deltaTime)
	{
		// change bars where needed
		if(m_curState != NyanimalStates.SLEEPING)
		{
			m_tiredMeter -= 0.05f * deltaTime;
			if(m_tiredMeter < 0)
			{
				ChangeState(NyanimalStates.SLEEPING);
			}
		}
		if(m_curState != NyanimalStates.EATING)
		{
			m_hungerMeter -= 0.5f * deltaTime;
			if(m_hungerMeter < 0)
			{
				m_hungerMeter = 0.0f;
			}
		}
		
		// handle what to do in each state
		switch(m_curState)
		{
		case IDLE:
			m_timeInState -= deltaTime;
			if(m_timeInState < 0.0f)
			{
				DeterminNextAction();
			}
			break;
		case WALKING:
			int[] curPos = getPosition();
			switch(m_curDirection)
			{
			case UP:
				curPos[1] -= m_walkSpeed * deltaTime;
				if(curPos[1] < m_destinationPoint)
				{
					DeterminNextAction();
				}
				break; 
			case DOWN:
				curPos[1] += m_walkSpeed * deltaTime;
				if(curPos[1] > m_destinationPoint)
				{
					DeterminNextAction();
				}
				break;
			case LEFT:
				curPos[0] -= m_walkSpeed * deltaTime;
				if(curPos[0] < m_destinationPoint)
				{
					DeterminNextAction();
				}
				break; 
			case RIGHT:
				curPos[0] += m_walkSpeed * deltaTime;
				if(curPos[0] > m_destinationPoint)
				{
					DeterminNextAction();
				}
				break;
			}
			SetPosition(curPos[0], curPos[1]);
			break;
		case SLEEPING:
			m_tiredMeter += 1.0f * deltaTime;
			if(m_tiredMeter > GetMeterMax())
			{
				m_tiredMeter = GetMeterMax();
				DeterminNextAction();
			}
			break;
		case EATING:
			m_hungerMeter += 5.0f * deltaTime;
			if(m_hungerMeter > GetMeterMax())
			{
				m_hungerMeter = GetMeterMax();
				DeterminNextAction();
			}
			break;
		};		
		
		UpdateAnimationFrame(deltaTime);

		int[] frameSize = m_Animations.get(m_curAnimation).GetCurrentFrameSize();
		SetSize(frameSize[0], frameSize[1]);
	}

	private void UpdateAnimationFrame(float deltaTime)
	{
		boolean newFrame = m_Animations.get(m_curAnimation).Update(deltaTime);
		
		// check if we have new UVs
		if(newFrame == true)
		{
			UpdateUV();
		}
	}

	private void UpdateUV()
	{
		// get the UV rect
		RectF frameRect = m_Animations.get(m_curAnimation).GetFrameUV(m_textureWidth, m_textureHeight, false);

		// put the rect where it belongs in the vertices
		// top right
		m_TriangleVerticesData[3] = frameRect.right;
		m_TriangleVerticesData[4] = frameRect.top;

		// bottom right
		m_TriangleVerticesData[8] = frameRect.right;
		m_TriangleVerticesData[9] = frameRect.bottom;

		// top left
		m_TriangleVerticesData[13] = frameRect.left;
		m_TriangleVerticesData[14] = frameRect.top;

		// bottom left
		m_TriangleVerticesData[18] = frameRect.left;
		m_TriangleVerticesData[19] = frameRect.bottom;

		// save
		m_TriangleVertices.position(0);
		m_TriangleVertices.put(m_TriangleVerticesData).position(0);
	}
	
	@Override
	public void Draw(CArgumentGroup arg)
	{
		// the OpenGL calls
		super.Draw(arg);
	}
}
